package q1.project.core.jpa;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.validation.constraints.NotNull;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

/**
 * SQL 执行服务 提供基于 JPQL 的通用查询方法，支持单条查询、列表查询、分页查询和计数查询
 *
 * <p><strong>SQL 注入防护说明：</strong>
 *
 * <ul>
 *   <li>本服务使用 JPA 的参数绑定机制（PreparedStatement），所有参数值都会被安全处理
 *   <li>通过 {@code setParameter()} 方法绑定的参数会自动进行转义，防止 SQL 注入攻击
 *   <li><strong>⚠️ 安全警告：</strong>如果 SQL 语句本身是动态拼接的（如直接拼接表名、字段名）， 仍可能存在 SQL 注入风险。建议：
 *   <li>1. SQL 语句中的表名、字段名应使用实体类名和属性名，不要使用字符串拼接
 *   <li>2. 所有用户输入的参数必须通过 Map 参数传递，使用命名参数（:paramName）
 *   <li>3. 不要直接将用户输入拼接到 SQL 语句中
 * </ul>
 *
 * <p><strong>连表查询支持：</strong>
 *
 * <ul>
 *   <li>JPQL 支持 INNER JOIN、LEFT JOIN、RIGHT JOIN 等所有标准 JOIN 操作
 *   <li>支持多表关联查询，可以查询关联实体
 *   <li>支持使用实体关联属性进行 JOIN（如：JOIN u.roles r）
 * </ul>
 *
 * <p>使用示例：
 *
 * <pre>{@code
 * // 注入服务
 * private final SqlExecuteService sqlExecuteService;
 *
 * // 单条查询
 * String sql = "SELECT u FROM UserEntity u WHERE u.username = :username";
 * Map<String, Object> params = Map.of("username", "admin");
 * UserEntity user = sqlExecuteService.findBySql(sql, params, UserEntity.class);
 *
 * // 列表查询
 * List<UserEntity> users = sqlExecuteService.findAllBySql(sql, params, UserEntity.class);
 *
 * // 连表查询（安全）
 * String joinSql = "SELECT u FROM UserEntity u " +
 *         "JOIN UserRoleEntity ur ON u.id = ur.userId " +
 *         "WHERE u.tenantId = :tenantId";
 * List<UserEntity> users = sqlExecuteService.findAllBySql(joinSql, params, UserEntity.class);
 *
 * // 分页查询（自动计算总数）
 * Page<UserEntity> page = sqlExecuteService.findAllBySql(
 *         sql, countSql, params, UserEntity.class, pageable);
 * }</pre>
 *
 * @author Q1 Team
 * @version 1.0
 */
@Service
public class SqlExecuteService {

  private final EntityManager entityManager;

  /**
   * 构造函数
   *
   * @param entityManager JPA EntityManager
   */
  public SqlExecuteService(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  /**
   * 执行 SQL 查询，返回单个结果
   *
   * @param sql JPQL 查询语句
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @param clz 返回类型
   * @param <T> 返回类型泛型
   * @return 查询结果对象
   * @throws jakarta.persistence.NoResultException 如果查询结果为空
   * @throws jakarta.persistence.NonUniqueResultException 如果查询结果不唯一
   */
  public <T> T findBySql(
      @NotNull String sql, Map<String, Object> parameters, @NotNull Class<T> clz) {
    TypedQuery<T> query = this.getTypedQuery(sql, parameters, clz);
    return query.getSingleResult();
  }

  /**
   * 执行 SQL 查询，返回结果列表
   *
   * @param sql JPQL 查询语句
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @param clz 返回类型
   * @param <T> 返回类型泛型
   * @return 查询结果列表
   */
  public <T> List<T> findAllBySql(
      @NotNull String sql, Map<String, Object> parameters, @NotNull Class<T> clz) {
    TypedQuery<T> query = this.getTypedQuery(sql, parameters, clz);
    return query.getResultList();
  }

  /**
   * 执行分页查询，手动传入总数
   *
   * @param sql JPQL 查询语句
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @param clz 返回类型
   * @param pageable 分页参数
   * @param totalCount 总记录数
   * @param <T> 返回类型泛型
   * @return 分页结果
   */
  public <T> PageImpl<T> findAllBySql(
      @NotNull String sql,
      Map<String, Object> parameters,
      @NotNull Class<T> clz,
      Pageable pageable,
      long totalCount) {
    TypedQuery<T> query = this.getTypedQuery(sql, parameters, clz);
    query.setFirstResult((int) (pageable.getPageNumber() * pageable.getPageSize()));
    query.setMaxResults(pageable.getPageSize());
    return new PageImpl<>(query.getResultList(), pageable, totalCount);
  }

  /**
   * 执行分页查询，自动计算总数
   *
   * @param sql JPQL 查询语句
   * @param countSql JPQL 计数查询语句
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @param clz 返回类型
   * @param pageable 分页参数
   * @param <T> 返回类型泛型
   * @return 分页结果
   */
  public <T> PageImpl<T> findAllBySql(
      @NotNull String sql,
      @NotNull String countSql,
      Map<String, Object> parameters,
      @NotNull Class<T> clz,
      Pageable pageable) {
    TypedQuery<T> query = this.getTypedQuery(sql, parameters, clz);
    query.setFirstResult((int) (pageable.getPageNumber() * pageable.getPageSize()));
    query.setMaxResults(pageable.getPageSize());
    return new PageImpl<>(query.getResultList(), pageable, this.count(countSql, parameters));
  }

  /**
   * 执行计数查询
   *
   * @param countSql JPQL 计数查询语句（如：SELECT COUNT(u) FROM UserEntity u WHERE ...）
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @return 总记录数
   */
  public long count(@NotNull String countSql, Map<String, Object> parameters) {
    TypedQuery<Long> query = this.getTypedQuery(countSql, parameters, Long.class);
    return query.getSingleResult();
  }

  /**
   * 创建 TypedQuery 并设置参数
   *
   * <p><strong>安全性说明：</strong>
   *
   * <ul>
   *   <li>使用 {@code setParameter()} 方法进行参数绑定，JPA 会自动使用 PreparedStatement
   *   <li>所有参数值都会被安全转义，防止 SQL 注入攻击
   *   <li>参数名必须与 SQL 中的命名参数（:paramName）完全匹配
   * </ul>
   *
   * @param sql JPQL 查询语句（使用命名参数，如 :paramName）
   * @param parameters 查询参数（Map格式，key为参数名，value为参数值）
   * @param clz 返回类型
   * @param <T> 返回类型泛型
   * @return TypedQuery 对象
   */
  private <T> TypedQuery<T> getTypedQuery(
      @NotNull String sql, Map<String, Object> parameters, @NotNull Class<T> clz) {
    TypedQuery<T> query = this.entityManager.createQuery(sql, clz);
    if (parameters != null && !parameters.isEmpty()) {
      Iterator<Map.Entry<String, Object>> iterator = parameters.entrySet().iterator();
      while (iterator.hasNext()) {
        Map.Entry<String, Object> entry = iterator.next();
        // 使用 setParameter 进行安全绑定，防止 SQL 注入
        query.setParameter(entry.getKey(), entry.getValue());
      }
    }
    return query;
  }
}
